package vip.manda.framework.web.server;

import cn.hutool.core.net.NetUtil;
import cn.hutool.core.thread.ThreadUtil;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import vip.manda.framework.config.TomcatConfig;
import vip.manda.framework.config.WebConfig;
import vip.manda.framework.core.Configs;
import vip.manda.framework.log.Log;
import vip.manda.framework.log.LogFactory;
import vip.manda.framework.web.http.CenterServlet;
import vip.manda.framework.web.http.Interceptor;

import javax.servlet.ServletException;
import java.util.concurrent.Executor;


/**
 * @author hongda.li 2022-04-01 11:25
 */
public class TomcatServer extends WebServer {
    private static final Log log = LogFactory.getLog();
    private static final String WORK = "/storage/tomcat/work";
    private final int port;
    private final String contextPath;
    private final Tomcat tomcat;
    private final String filter;
    private final String servlet;

    public TomcatServer() {
        initConfig();
        this.port = Configs.getInt(WebConfig.PORT);
        this.contextPath = Configs.getStr(WebConfig.CONTEXT);
        this.tomcat = new Tomcat();
        this.filter = Interceptor.class.getName();
        this.servlet = CenterServlet.class.getName();
    }

    private void initConfig() {
        Configs.setDefaultConfig(TomcatConfig.SILENT, TomcatConfig.Value.SILENT);
        Configs.setDefaultConfig(TomcatConfig.ACCEPTOR_THREAD_COUNT, TomcatConfig.Value.ACCEPTOR_THREAD_COUNT);
        Configs.setDefaultConfig(TomcatConfig.ACCEPT_COUNT, TomcatConfig.Value.ACCEPT_COUNT);
        Configs.setDefaultConfig(TomcatConfig.MAX_THREADS, TomcatConfig.Value.MAX_THREADS);
        Configs.setDefaultConfig(TomcatConfig.MAX_CONNECTIONS, TomcatConfig.Value.MAX_CONNECTIONS);
        Configs.setDefaultConfig(TomcatConfig.MIN_THREADS, TomcatConfig.Value.MIN_THREADS);
        Configs.setDefaultConfig(TomcatConfig.KEEP_ALIVE_TIMEOUT, TomcatConfig.Value.KEEP_ALIVE_TIMEOUT);
        Configs.setDefaultConfig(TomcatConfig.CONNECTION_TIMEOUT, TomcatConfig.Value.CONNECTION_TIMEOUT);
    }

    @Override
    public void startServer() {
        ThreadUtil.execute(() -> {
            try {
                start();
            } catch (ServletException | LifecycleException e) {
                e.printStackTrace();
                log.error("Tomcat start error");
            }
        });
    }

    @Override
    public void stopServer() {
        try {
            Executor executor = this.tomcat.getConnector().getProtocolHandler().getExecutor();
            ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
            threadPoolExecutor.shutdownNow();
            this.tomcat.stop();
        } catch (LifecycleException e) {
            log.debug("Tomcat stop error");
            e.printStackTrace();
        }
    }

    public void start() throws ServletException, LifecycleException {
        configTomcat();
        tomcat.start();
        tomcat.getServer().await();
    }

    private Connector configConnector(int port) {
        Connector connector = new Connector("HTTP/1.1");
        connector.setPort(port);
        connector.setURIEncoding("UTF-8");
        connector.setProperty(TomcatConfig.ACCEPTOR_THREAD_COUNT, Configs.getStr(TomcatConfig.ACCEPTOR_THREAD_COUNT));
        connector.setProperty(TomcatConfig.ACCEPT_COUNT, Configs.getStr(TomcatConfig.ACCEPT_COUNT));
        connector.setProperty(TomcatConfig.MAX_THREADS, Configs.getStr(TomcatConfig.MAX_THREADS));
        connector.setProperty(TomcatConfig.MAX_CONNECTIONS, Configs.getStr(TomcatConfig.MAX_CONNECTIONS));
        connector.setProperty(TomcatConfig.MIN_THREADS, Configs.getStr(TomcatConfig.MIN_THREADS));
        connector.setProperty(TomcatConfig.KEEP_ALIVE_TIMEOUT, Configs.getStr(TomcatConfig.KEEP_ALIVE_TIMEOUT));
        connector.setProperty(TomcatConfig.CONNECTION_TIMEOUT, Configs.getStr(TomcatConfig.CONNECTION_TIMEOUT));
        return connector;
    }

    private void configServlet(Context context) {
        tomcat.addServlet(contextPath, servlet, servlet);
        context.addServletMappingDecoded("/*", servlet);
    }

    private void configFilter(Context context) {
        FilterDef filterDef = new FilterDef();
        filterDef.setFilterClass(filter);
        filterDef.setFilterName(filter);
        context.addFilterDef(filterDef);
        FilterMap filterMap = new FilterMap();
        filterMap.addURLPattern("/*");
        filterMap.addServletName(servlet);
        filterMap.setFilterName(filter);
        context.addFilterMap(filterMap);
    }

    private void configTomcat() {
        tomcat.setSilent(Configs.getBool(TomcatConfig.SILENT));
        tomcat.setBaseDir(WORK);
        tomcat.setPort(port);
        Connector connector = configConnector(port);
        tomcat.setConnector(connector);
        Context context = tomcat.addContext(contextPath, null);
        configFilter(context);
        configServlet(context);
        log.info("Server listen on[http://{}:{}{}]", NetUtil.getLocalhostStr(), port, contextPath);
    }
}
